<?php

/*
*
*  This file is part of ValerieEngine
*  ValerieEngine is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  ValerieEngine is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with ValerieEngine.  If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
 * Handler Class for PHP Errors
 *
 * @author Dutchak Vitaly
 *
 */

class ErrorHandler {

    static private $_shutdownErrors = array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR);
    static private $_displayErrors = array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
    static private $_displayWarnings = array(E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE);
    static private $_errorNames = array(
        E_ERROR => 'Fatal Error',
        E_PARSE => 'Parse Error',
        E_CORE_ERROR => 'Fatal Core Error',
        E_COMPILE_ERROR => 'Fatal Compile Error',
        E_USER_ERROR => 'Fatal Error',
        E_WARNING => 'Warning',
        E_NOTICE => 'Notice',
        E_USER_NOTICE => 'Notice',
        E_USER_WARNING => 'Warning',
    );
    static private $_isDebug = false;
    static private $_count = 0;
    static private $_noticesAndWarnings = array();


    /**
     * Init handler for all type errors
     * @static
     * @return void
     */
    final static function Init() {

        self::CheckIsProduction();
        error_reporting(E_PARSE);
        set_error_handler(
            array("ErrorHandler", "CatchError"),
            E_USER_ERROR | E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE
        );
        set_exception_handler(array("ErrorHandler", "CatchException"));
        ob_start(array("ErrorHandler", "CheckParseErrors"));
        register_shutdown_function(array("ErrorHandler", "CheckLastError"));
 
    }

    /**
     * Catch Error, Warining and Notices
     * @static
     * @throws CoreException
     *
     * @param int $type - error level
     * @param string $message - error message
     * @param string $file - error file
     * @param string $line - error line
     * @return true
     */
    final static function CatchError($type, $message, $file, $line) {
        if (in_array($type, self::$_displayErrors)) {
            throw new CoreException($message, $type, $file, $line);
        } elseif (in_array($type, self::$_displayWarnings)) {
            self::CatchWarningAndNotice($type, $message, $file, $line);
        }
        return true;
    }

    /**
     * Catch method for E_NOTICE, E_WARNING, E_USER_NOTICE, E_USER_WARNING
     * @param int $type - error level
     * @param string $message - error message
     * @param string $file - error file
     * @param string $line - error line
     * @return true
     */
    final static function CatchWarningAndNotice($type, $message, $file, $line) {
        if (in_array($type, self::$_displayWarnings) && self::$_isDebug) {
            $trace = array_slice(debug_backtrace(), 3);
            $error = array('type' => $type, 'message' => $message, 'file' => $file, 'line' => $line);
            self::$_noticesAndWarnings[] = self::GetDebugBacktrace($error, $trace);
        }
        return true;
    }

    /**
     * Handler method for E_ERROR level errors
     * @static
     * @return false
     */

    final static function CheckLastError() {

        $error = error_get_last();
        if (in_array($error['type'], self::$_shutdownErrors)) {
            $errorBlock = self::GetDebugBacktrace($error);
            if (self::$_isDebug) {
                echo $errorBlock;
            } else {
                self::SendError2Admin($errorBlock);
                //placeholder
            }
        } 
        return false;
    }

    /**
     * Parse Output from buffer and search for E_PARSE level error
     * and add all warning and notices before content
     *
     * @static
     * @param string $buffer buffer for parse
     * @return string
     */

    final static function CheckParseErrors($buffer) {

        if (mb_stripos($buffer, 'Parse Error', 0, 'utf-8') !== false) {
            $error = error_get_last();
            $errorBlock = self::GetDebugBacktrace($error);
            if (self::$_isDebug) {
                return $errorBlock;
            } else {
                self::SendError2Admin($errorBlock);
                //placeholder
                return '';
            }
        }

        if (count(self::$_noticesAndWarnings) > 0) {
            $html = "<div style='padding:5px;background-color:#FFFBBC;border:1px solid silver;'>
                        <b>WARNINGS AND NOTICES (" . count(self::$_noticesAndWarnings) . ")</b>
                        <b>
                            <a href='javascript://' id='toggle-warnings-link' onclick='ToggleWarnings()' >show(+)</a>
                        </b>
                        <div id='warnings-and-notices' style='display:none'>
                            " . (implode("", self::$_noticesAndWarnings)) . "
                        </div>
                     </div>";
            $html .= "<script>
                        function ToggleWarnings(){
                            var d = document.getElementById('warnings-and-notices');
                            var link = document.getElementById('toggle-warnings-link');
                            if (d.style.display=='none'){
                                d.style.display = 'block';
                                link.innerHTML = 'hide(-)';
                            }else{
                                d.style.display = 'none';
                                link.innerHTML = 'show(+)';
                            }
                        }
                    </script>";
            $buffer = $html . $buffer;
        }
        
        if (self::$_isDebug === true) {

            $buffer .= '<div style="text-align: center;">'
                       . '   <small> Executed: ' . VEngine::GetEngineRunningTime() . ' seconds</small> <br />'
                       . '   <small> Memory Usage: ' . VEngine::GetMemoryUsage() . ' Mb</small> <br />'
                       . '   <small> Peak Memory Usage: ' . VEngine::GetMemoryPeakUsage() . 'Mb</small>'
                       . '</div>';
        }

        return $buffer;
    }

    /**
     * Return a backtrace HTML block
     * @static
     * @param array $errorData - error data array
     * @param array $trace - trace data
     * @param bool $isHighlight - flag
     * @return string
     */
    final static function GetDebugBacktrace($errorData, $trace = array(), $isHighlight = true) {

        self::$_count++;

        $ondblclick = '';
        $addscript = '';

        if (in_array($errorData['type'], self::$_displayWarnings)) {

            $ondblclick = "ondblclick='var d = document.getElementById(\"backtrace_" . self::$_count . "\");if(d.style.display == \"none\"){d.style.display = \"block\";}else{d.style.display = \"none\";}'";
            $addscript = "document.getElementById('backtrace_" . self::$_count . "').style.display = 'none';";
        }

        $res = "<div id='debug_info' style='background:#e2e2e2;border:1px solid #999;padding:5px;margin:5px;font: 12px/18px Verdana;' {$ondblclick}><b> #(" . self::$_count . ") ###ERROR_MSG###</b><div id='backtrace_" . self::$_count . "' style='padding:10px;display:block;margin:10px;'>###BACKTRACE###</div></div>";

        $date = date('H:i:s d-m-Y');

        $errorMessage = "[{$date}] " . self::$_errorNames[$errorData['type']] . " : {$errorData['message']} in file \"{$errorData['file']}\" at line {$errorData['line']}";

        $code = self::GetSourceCodeFromFileAroundLine($errorData['file'], $errorData['line'], $isHighlight);

        $errorMessage .= "<br />" . $code . "<br />";

        $backTrace = '';

        $JS = '';
        if (!empty($trace)) {

            $backTrace = '<center><h3>DEBUG BACKTRACE</h3></center>';

            foreach ($trace as $n => $step) {

                $step['class'] = (isset($step['class'])) ? $step['class'] : '';
                $step['type'] = (isset($step['type'])) ? $step['type'] : '';

                if (!$step['file']) {
                    $file = __FILE__;
                } else {
                    $file = $step['file'];
                }

                $line = '';
                if ($step['line']) {
                    $line = ' at line ' . $step['line'];
                }

                if ($step['class']) {
                    $function = 'Method: ';
                } else {
                    $function = 'Function: ';
                }

                if (!$step['function']) {
                    $step['function'] = '{main}';
                }


                if (isset($step['object'])) {
                    $call = get_class($step['object']);
                } else {
                    $call = $step['class'];
                }

                $args = array();
                $argsDebug = '';

                if ($step['args'])
                    foreach ($step['args'] as $k => $arg) {
                        $type = gettype($arg);

                        switch ($type) {
                            case "string":
                                $l = mb_strlen($arg, 'UTF-8');
                                $r = "string({$l})";
                                break;
                            case "integer":
                                $r = "int";
                                break;
                            case "boolean":
                                $r = "boolean";
                                $arg = ($arg) ? 'true' : 'false';
                                break;
                            case "double":
                                $r = "double";
                                break;
                            case "array":
                                $r = "array(" . count($arg) . ")";
                                break;
                            case "object":
                                $r = "object(" . (get_class($arg)) . ")";
                                break;
                            case "resource" :
                                $r = "resource(" . (get_resource_type($arg)) . ")";
                                break;
                            case "NULL":
                                $r = "NULL";
                                $arg = "NULL";
                                break;
                            default:
                                $r = "unknown type";
                                break;
                        }
                        $c = self::$_count;
                        $args[] = $r;
                        if (($type == 'array' || $type == 'object' || ($type == 'string' && mb_strlen($arg, 'UTF-8') > 100)) && $isHighlight) {

                            $argsDebug .= '<div style="padding:5px;background:#FFFBBC;border: 1px dashed silver;margin:5px;font:10px/12px Verdana;">
                                            <b>Argument (#' . ($k + 1) . '), Type: [' . $r . ']
                                            <a href="javascript://" id="toggle-argument-link-' . $k . '-' . $n . '-' . $c . '" onclick="ToggleArgument(' . $k . ',' . $n . ',' . $c . ')"></a></b>
                                            <div id="argument-' . $k . '-' . $n . '-' . $c . '"><pre style="font-size:12px;line-height:16px;padding:5px;">' . (print_r($arg, true)) . '</pre></div>
                                       </div>';
                            $JS .= "ToggleArgument({$k},{$n},{$c}); \n";
                        } else {
                            $argsDebug .= '<div style="padding:5px;background:#FFFBBC;border: 1px dashed silver;margin:5px;font:10px/12px Verdana;">
                                            <b>Argument (#' . ($k + 1) . '), Type: [' . $r . ']  Value: [' . $arg . '] </div></div>';
                        }
                    }

                $call .= $step['type'] . $step['function'] . "(<small>" . implode(", ", $args) . "</small>)";

                $code = self::GetSourceCodeFromFileAroundLine($file, $step['line'], $isHighlight);

                $backTrace .= "<div style='background:#FFFFFF;border:1px dashed black;padding:5px; margin:5px;'><div style='background:#FFAAAA;text-align:center;padding:5px;font-weight:bold'>Step: #{$n} file: [<font color=blue>{$file}</font>] {$line} </div>
                    <table width='100%'>
                        <tr><td width='200' style='font:12px/18px Verdana'>Class</td> <td>{$step['class']}</td></tr>
                        <tr><td width='200' style='font:12px/18px Verdana'>{$function}</td> <td>{$step['function']}</td></tr>
                        <tr><td width='200' style='font:12px/18px Verdana'>Call</td> <td>$call</td></tr>
                        <tr><td colspan='2' width='100%'>
                            {$argsDebug}
                        </td></tr>
                    </table>
                        <div style='border: 1px dashed blue;background:#EEEEEE;padding:5px;'>
                            {$code}
                        </div>
                    </div>
                    ";
            }
        } else {

            $backTrace = '<center><h3>NO BACKTRACE FOR THIS ERROR</h3></center>';
        }


       # $memory = (ceil((memory_get_usage() / 1024 / 1024) * 100) / 100) . "MB";
       # $time = array_sum(explode(' ', microtime())) - $GLOBALS['start'];

        //$backTrace .= "";

        if (!isset($_SESSION))
            session_start();

        //need to refactor
        if ($isHighlight) {
            $backTrace .= "<div style='background:#FFF0D8; border: 1px dashed silver;padding:5px;margin:5px;'><b>\$_POST ARRAY  <a href='javascript://' id='_post_more_link_" . self::$_count . "' onclick='ToggleInfo(\"post\"," . self::$_count . ")'></a></b><div id='_post_data_" . self::$_count . "'><pre>" . print_r($_POST, true) . "</pre></div></div>";
            $backTrace .= "<div style='background:#FFF0D8; border: 1px dashed silver;padding:5px;margin:5px;'><b>\$_GET ARRAY <a href='javascript://' id='_get_more_link_" . self::$_count . "' onclick='ToggleInfo(\"get\"," . self::$_count . ")'></a></b> <div id='_get_data_" . self::$_count . "'><pre>" . print_r($_GET, true) . "</pre></div></div>";
            $backTrace .= "<div style='background:#FFF0D8; border: 1px dashed silver;padding:5px;margin:5px;'><b>\$_SESSION ARRAY <a href='javascript://' id='_session_more_link_" . self::$_count . "' onclick='ToggleInfo(\"session\"," . self::$_count . ")'></a></b> <div id='_session_data_" . self::$_count . "'><pre>" . print_r($_SESSION, true) . "</pre></div></div>";
            $backTrace .= "<div style='background:#FFF0D8; border: 1px dashed silver;padding:5px;margin:5px;'><b>\$_FILES ARRAY <a href='javascript://' id='_file_more_link_" . self::$_count . "' onclick='ToggleInfo(\"file\"," . self::$_count . ")'></a></b> <div id='_file_data_" . self::$_count . "'><pre>" . print_r($_FILES, true) . "</pre></div></div>";
            $backTrace .= "<div style='background:#FFF0D8; border: 1px dashed silver;padding:5px;margin:5px;'><b>\$_SERVER ARRAY <a href='javascript://' id='_server_more_link_" . self::$_count . "' onclick='ToggleInfo(\"server\"," . self::$_count . ")'></a></b> <div id='_server_data_" . self::$_count . "'><pre>" . print_r($_SERVER, true) . "</pre></div></div>";
        }

        $script = "<script>
                            {$addscript}
                            function ToggleInfo(info,num){
                                var d = document.getElementById('_'+info+'_data_'+num);

                                if (d.style.display == 'none'){
                                    d.style.display = 'block';
                                    document.getElementById('_'+info+'_more_link_'+num).innerHTML = 'hide (-)';
                                }else{
                                    d.style.display = 'none';
                                    document.getElementById('_'+info+'_more_link_'+num).innerHTML = 'show (+)';
                                }
                            }

                            ToggleInfo('post'," . self::$_count . ");
                            ToggleInfo('get'," . self::$_count . ");
                            ToggleInfo('session'," . self::$_count . ");
                            ToggleInfo('file'," . self::$_count . ");
                            ToggleInfo('server'," . self::$_count . ");
                       </script>
                       <script>
                            function ToggleArgument(arg,step,warn){
                                var d = document.getElementById('argument-'+arg+'-'+step+'-'+warn);
                                var link = document.getElementById('toggle-argument-link-'+arg+'-'+step+'-'+warn);

                                if (d.style.display == 'none'){
                                    d.style.display = 'block';
                                    link.innerHTML = 'hide(-)';
                                }else{
                                    d.style.display = 'none';
                                    link.innerHTML = 'show(+)';
                                }

                            }
                            {$JS}
                    </script>
                       ";

        $res = str_replace(array('###ERROR_MSG###', '###BACKTRACE###'), array($errorMessage, $backTrace), $res) . $script;

        return $res;
    }

    /**
     * Exception method handler
     * @static
     * @param Exception $e
     * @return bool
     */
    final static function CatchException($e) {

        if (is_a($e, "CoreException")) {
            $trace = array_slice($e->getTrace(), 3);
        } else {
            $trace = $e->getTrace();
        }

        $error = array('type' => $e->getCode(), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine());

        $errorBlock = self::GetDebugBacktrace($error, $trace);

        if (in_array($error['type'], self::$_displayWarnings)) {
            echo $errorBlock;
            return false;
        }

        if (self::$_isDebug) {
            echo $errorBlock;
        } else {
            self::SendError2Admin($errorBlock);
            //placeholder
            echo '';
        }
        return false;
    }

    /**
     * Check is this production or not
     * @static
     * @return void
     */
    private final static function CheckIsProduction() {

        self::$_isDebug = is_file(ROOT . '/DEBUG');

    }

    /**
     * Send an HTML code of error to admin
     * @static
     * @param string $html - html code for mail
     * @return void
     */
    private final static function SendError2Admin($html) {

        $text = date('Y-m-d H:i:s') . "<br />";
        $text .= $html;
        $text .= "<br />";
        $text .= "<br />";
        $text .= 'URI: http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . "<br />";
        $text .= 'REFERER : ' . $_SERVER['HTTP_REFERER'] . "<br />";

        //mail_to
    }

    final static function GetSourceCodeFromFileAroundLine($file, $line, $isHighlight = true) {
        
        $sCode = file($file);
        $total = count($sCode);

        $start = $line - 8;
        $end = $line + 8;

        if ($start < 1) {
            $start = 1;
        }
        if ($end > $total + 1) {
            $end = $total + 1;
        }

        $code = '<table width="100%" cellpadding="0" cellspacing="0">';

        for ($i = $start; $i <= $end; $i++) {
            $l = $sCode[$i - 1];

            if ($i == $line) {
                $code .= '<tr bgcolor="#f9dede"><td><small><b>' . ($i) . ':</small></b> </td><td width="100%">';
            } else {
                $code .= '<tr><td  bgcolor="#efeefe"><small>' . ($i) . ':<small> </td><td bgcolor="#EEEEEE" width="100%">';
            }

            if (!$isHighlight)
                $temp = "<nobr>" . htmlspecialchars($l) . "</nobr>";
            else
                $temp = "<nobr>" . (str_replace(array("&lt;?php", "<br />"), "", highlight_string("<?php " . $l, true))) . "</nobr>";

            $code .= $temp . '</td></tr>';
        }
        $code .= '</table>';

        return $code;
    }

}

?>